package com.enfi.api.modules.db.base;

import com.enfi.api.domain.res.ApiResult;
import com.enfi.api.util.SecUser;
import com.enfi.api.util.UserUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import org.dozer.DozerBeanMapper;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author wind
 * @since 2020-04-07 15:47:34
 */
public abstract class BaseController<B extends BaseService,T extends BaseVO<ID>,G extends BaseQuery,ID> {

    protected B baseService;

    @Autowired
    protected HttpServletRequest request;

    @Autowired
    protected HttpServletRequest response;

    @Autowired
    protected DozerBeanMapper beanMapper;

    @Autowired
    protected void setBaseService(B b) {
        this.baseService = b;
    }

    @InitBinder
    public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder){

    }

    protected Map<HttpMethod,List<String>> supportMethodMap= Maps.newHashMap();

    @PostConstruct
    public void postConstruct(){
        init();
    }
    protected  void init(){
        supportMethodMap.put(HttpMethod.GET,Lists.newArrayList(""));
        supportMethodMap.put(HttpMethod.PUT,Lists.newArrayList(""));
        supportMethodMap.put(HttpMethod.POST,Lists.newArrayList("query","batch",""));
        supportMethodMap.put(HttpMethod.PATCH,Lists.newArrayList("id","batch",""));
        supportMethodMap.put(HttpMethod.DELETE,Lists.newArrayList(""));
    }
    public boolean supportMethod(HttpMethod httpMethod,String params){
        List<String> flag=supportMethodMap.getOrDefault(httpMethod,null);
       if(flag!=null&&flag.contains(params)){
           return true;
       }else {
           return false;
       }
    }

    @GetMapping("{id}")
    @SneakyThrows
    public Object getById(@PathVariable ID id) {
        if(supportMethod(HttpMethod.GET,"")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        return ResponseEntity.ok(getAttributes(HttpStatus.OK, render(RequestMethod.GET.name(), this.baseService.findById(id))));
    }

    @GetMapping
    @SneakyThrows
    public Object get(@Validated({T.IGroupGet.class}) T t,Pageable pageable) {
        if(supportMethod(HttpMethod.GET,"")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        Object o = this.baseService.getEntityClass().newInstance();
        copyProperties(t, o);
        t.init(request.getParameterMap());
        return ResponseEntity.ok(getAttributes(HttpStatus.OK, render(RequestMethod.GET.name(), this.baseService.find(t.getQuery(),o, pageable))));
    }

    @PostMapping(params = "query")
    @SneakyThrows
    public Object get(@Validated({T.IGroupGetPost.class}) @RequestBody(required = false) G g) {
        if(supportMethod(HttpMethod.GET,"query")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        Assert.notNull(g, "缺少请求实体内容");
        Object o = this.baseService.getEntityClass().newInstance();
        return ResponseEntity.ok(getAttributes(HttpStatus.OK, render(RequestMethod.GET.name(),this.baseService.find(g,o))));
    }

    @PostMapping
    @SneakyThrows
    public ResponseEntity post(@Validated({T.IGroupPost.class}) @RequestBody(required = false) T t) {
        if(supportMethod(HttpMethod.POST,"")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Assert.notNull(t, "缺少请求实体内容");
        Object o = this.baseService.getEntityClass().newInstance();
        copyProperties(t, o);
        this.baseService.insert(o);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PostMapping(params = "batch")
    @SneakyThrows
    public ResponseEntity postBatch(@Validated({T.IGroupPost.class}) @RequestBody(required = false) List<T> t) {
        if(supportMethod(HttpMethod.POST,"batch")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Assert.notEmpty(t, "缺少请求实体内容");
        List list=Lists.newArrayList();
        for (T t1:t){
            Object o = this.baseService.getEntityClass().newInstance();
            copyProperties(t, o);
            list.add(o);
        }
        this.baseService.insertAll(list);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PutMapping(params = "batch")
    @SneakyThrows
    public Object putBatch(@Validated({T.IGroupPut.class}) @RequestBody(required = false) List<T> t) {
        if(supportMethod(HttpMethod.PUT,"batch")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        Assert.notEmpty(t, "缺少请求实体内容");
        List list= Lists.newArrayList();
        for (T t1:t){
            Optional o = this.baseService.findById(t1.getId());
            Assert.notNull(o.isPresent(), "资源缺失");
            Object o1 = this.baseService.getEntityClass().newInstance();
            copyProperties(o1, t1);
            list.add(o1);
        }
        this.baseService.update(list,HttpMethod.PUT);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PutMapping
    @SneakyThrows
    public Object put(@Validated({T.IGroupPut.class}) @RequestBody(required = false) T t) {
        if(supportMethod(HttpMethod.PUT,"")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        Assert.notNull(t, "缺少请求实体内容");
        Optional o = this.baseService.findById(t.getId());
        Assert.notNull(o.isPresent(), "资源缺失");
        copyProperties(t, o.get());
        this.baseService.update(o.get(),HttpMethod.PUT);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PatchMapping(params = "batch")
    @SneakyThrows
    public Object patchBatch(@Validated({T.IGroupPut.class}) @RequestBody(required = false) List<T> t) {
        if(supportMethod(HttpMethod.PATCH,"batch")){
            return getAttributes(HttpStatus.FORBIDDEN);
        }
        Assert.notEmpty(t, "缺少请求实体内容");
        List list= Lists.newArrayList();
        for (T t1:t){
            Optional o = this.baseService.findById(t1.getId());
            Assert.notNull(o.isPresent(), "资源缺失");
            Object o1 = this.baseService.getEntityClass().newInstance();
            copyProperties(o1, t1);
            list.add(o1);
        }
        this.baseService.update(list,HttpMethod.PATCH);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PatchMapping(params = "id")
    @SneakyThrows
    public ResponseEntity patchByParams(@Validated({T.IGroupPatch.class}) T t) {
        if(supportMethod(HttpMethod.PATCH,"id")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Optional o = this.baseService.findById(t.getId());
        Assert.isTrue(o.isPresent(), "资源缺失");
        copyProperties(t, o.get());
        this.baseService.update(o.get(),HttpMethod.PATCH);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @PatchMapping
    @SneakyThrows
    public ResponseEntity patch( @RequestBody(required = false) T t) {
        if(supportMethod(HttpMethod.PATCH,"")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Assert.notNull(t, "缺少请求实体内容");
        Optional o = this.baseService.findById(t.getId());
        Assert.isTrue(o.isPresent(), "资源缺失");
        copyProperties(t, o.get());
        this.baseService.update(o.get(), HttpMethod.PATCH);
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @DeleteMapping
    @SneakyThrows
    public ResponseEntity delete(@Validated({T.IGroupDelete.class}) @RequestBody(required = false) T t) {
        if(supportMethod(HttpMethod.DELETE,"")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Assert.notNull(t, "缺少请求实体内容");
        Optional o = this.baseService.findById(t.getId());
        Assert.isTrue(o.isPresent(), "资源缺失");
        copyProperties(t, o.get());
        this.baseService.remove(o.get());
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    @DeleteMapping("{id}")
    public ResponseEntity deleteByPath(@PathVariable ID id) {
        if(supportMethod(HttpMethod.DELETE,"")){
            return ResponseEntity.ok(getAttributes(HttpStatus.FORBIDDEN));
        }
        Optional o = this.baseService.findById(id);
        Assert.isTrue(o.isPresent(), "资源缺失");
        this.baseService.remove(o.get());
        return ResponseEntity.ok(getAttributes(HttpStatus.OK));
    }

    protected void copyProperties(Object source, Object target) {
        // BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
        this.beanMapper.map(source,target);
    }

    private String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<String>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    protected Map getAttributes(HttpStatus httpStatus) {
        return getDefaultAttributes(httpStatus, null,null);
    }

    protected Map getAttributes(HttpStatus httpStatus, Object o) {
        return getDefaultAttributes(httpStatus, o,null);
    }
    protected Map getErrorAttributes(HttpStatus httpStatus, String message) {
        return getDefaultAttributes(httpStatus, null,message);
    }
    protected Map getDefaultAttributes(HttpStatus httpStatus, Object o,String message) {
        Map<String, Object> attributes = new LinkedHashMap();
        attributes.put("status", httpStatus.value());
        if (o != null) {
            attributes.put("data", o);
        }
        attributes.put("timestamp", new Date());
        attributes.put("error", "");
        attributes.put("exception", "");
        if(httpStatus.equals(HttpStatus.OK)){
            attributes.put("message", "ok");
        }else{
            attributes.put("message", message==null?"":message);
        }
        SecUser secUser=UserUtils.getSecUserOrNo();
        attributes.put("operator", secUser==null?"":secUser.getUsername());
        attributes.put("path", request.getRequestURI());
        return attributes;
    }

    protected Object render(String method, Object o) {
        return o;
    }
}